/*
 *  linux/kernel/signal.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>

#include <signal.h>

void do_exit(int error_code);

// 获取当前任务信号屏蔽位图（屏蔽码或阻塞码）。sgetmask 可分解为 signal-get-mask。
int sys_sgetmask()
{
	return current->blocked;
}

// 设置新的信号屏蔽位图。SIGKILL 不能被屏蔽。返回值是原信号屏蔽位图。
int sys_ssetmask(int newmask)
{
	int old=current->blocked;

	// SIGKILL信号不可被屏蔽
	current->blocked = newmask & ~(1<<(SIGKILL-1));
	return old;
}

// 保存老的信号句柄：复制 sigaction 数据到fs数据段 to 处。
// 即从内核空间复制到用户（任务）数据段中。
static inline void save_old(char * from,char * to)
{
	int i;

	verify_area(to, sizeof(struct sigaction));
	for (i=0 ; i< sizeof(struct sigaction) ; i++) {
		put_fs_byte(*from,to);
		from++;
		to++;
	}
}

// 获取新的信号句柄：把 sigaction 数据从fs 数据段 from 位置复制到 to 处。
// 即从用户数据空间复制到内核数据段中。
static inline void get_new(char * from,char * to)
{
	int i;

	for (i=0 ; i< sizeof(struct sigaction) ; i++)
		*(to++) = get_fs_byte(from++);
}

// 为指定的信号安装新的信号句柄(信号处理程序，用户指定的函数)。
int sys_signal(int signum, long handler, long restorer)
{
	struct sigaction tmp;

	if (signum<1 || signum>32 || signum==SIGKILL)
		return -1;
	tmp.sa_handler = (void (*)(int)) handler;
	tmp.sa_mask = 0; // 不屏蔽任何信息。sa_mask 是执行信号处理句柄时的信号屏蔽码，这个一个信号句柄被调用时，我们就可以阻塞指定的一些信号。
	tmp.sa_flags = SA_ONESHOT | SA_NOMASK; // 信号选项标志位设定为使用一次后就要恢复成默认值，并且在信号处理过程中该信号也能被接受
	tmp.sa_restorer = (void (*)(void)) restorer;
	handler = (long) current->sigaction[signum-1].sa_handler;
	current->sigaction[signum-1] = tmp;
	return handler;
}

// 其实他们的区别还是很容易看出的，signal()函数的话每次调用之后就会恢复成默认处理函数，
// 需要在信号处理程序中再调用一次signal()函数才能继续使用用户自己设置的处理函数，
// 但在下次设置之前，来的信号就会使用默认操作导致丢失。而sigaction()设置后能一直保持用户设置的处理函数。
int sys_sigaction(int signum, const struct sigaction * action,
	struct sigaction * oldaction)
{
	struct sigaction tmp;

	if (signum<1 || signum>32 || signum==SIGKILL)
		return -1;
	//在信号的 sigaction 结构中设置新的句柄。
	tmp = current->sigaction[signum-1];
	get_new((char *) action,
		(char *) (signum-1+current->sigaction));
	// 如果 oldaction 指针不为空的话，则将原函数指针（句柄）保存到 oldaction 所指的位置。
	if (oldaction)
		save_old((char *) &tmp,(char *) oldaction);
	// 如果允许信号在自己的信号句柄中收到，则令屏蔽码为0，否则设置屏蔽本信号。
	// 再来理解下最后这个屏蔽的意思：假如设置了这个屏蔽，那在整个信号处理过程中，
	// 即使又来了一个同样的信号在位图中置位，也不会立刻再次调用信号处理函数，而是要等待当前处理函数结束先。
	// 另外，如果有多个信号过来，但位图只有一位，只能保存1个信号，其他的会丢失。
	if (current->sigaction[signum-1].sa_flags & SA_NOMASK) /* 如果sa_flags中包含SA_NOMASK选项 */
		current->sigaction[signum-1].sa_mask = 0;  /* 不需要屏蔽本信号 */
	else
		/* sa_mask表示一个信号集，在调用该信号句柄之前，这一信号集要加进进程的信号屏蔽字中。仅当从信号句柄返回时再将进程的信号屏蔽字复位为原先值。*/
		current->sigaction[signum-1].sa_mask |= (1<<(signum-1));
	return 0;
}

// 系统调用完后（iret之前）的信号预处理
// 堆栈信息参考：图8-12
void do_signal(long signr,long eax, long ebx, long ecx, long edx,
	long fs, long es, long ds,
	long eip, long cs, long eflags,
	unsigned long * esp, long ss)
{
	unsigned long sa_handler;
	long old_eip=eip;  /* 修改eip之前，先备份 */
	struct sigaction * sa = current->sigaction + signr - 1;
	int longs;
	unsigned long * tmp_esp;

	// 将信号处理句柄指针转换成long类型，方便条件对比
	sa_handler = (unsigned long) sa->sa_handler;
	if (sa_handler==1) /* 1 表示SIG_IGN ; 0 表示 SIG_DFL */
		return;
	if (!sa_handler) {
		if (signr==SIGCHLD)
			return;
		else
			do_exit(1<<(signr-1));
	}
	// 如果待处理信号既不是SIG_IGN，也不是SIG_DFL
	if (sa->sa_flags & SA_ONESHOT) /* sa_flags中包含SA_ONESHOT选项 */
		sa->sa_handler = NULL; /*如果该信号句柄只需使用一次，则将该句柄置空。*/
	// 由于 C 函数是传值函数，因此给eip赋值时需要使用'*(&eip)'的形式。???
	// 这里请注意，使用如下方式对普通C函数参数进行修改是不起作用的。
	// 因为当函数返回时堆栈上的参数将会被调用者丢弃。这里之所以可以使用这种方式，是因为该函数是从汇编程序中被调用的，
	// 并且在函数返回后汇编程序并没有把调用 do signal() 时的所有参数都丢弃。eip等仍然在堆栈中。
	*(&eip) = sa_handler; /* 修改内核态堆栈上 eip 为指向信号处理句柄。(用户程序返回地址 （eip、cs）被保存在用户态栈中) */
	
	// 扩展用户态的堆栈（esp）
	longs = (sa->sa_flags & SA_NOMASK)?7:8;
	*(&esp) -= longs;
	verify_area(esp,longs*4);
	tmp_esp=esp;
	// 开始向用户态堆栈压入数据
	put_fs_long((long) sa->sa_restorer,tmp_esp++);
	put_fs_long(signr,tmp_esp++);
	if (!(sa->sa_flags & SA_NOMASK))
		put_fs_long(current->blocked,tmp_esp++);  /* 将原始的blocked压栈，以便后续恢复 */
	put_fs_long(eax,tmp_esp++);
	put_fs_long(ecx,tmp_esp++);
	put_fs_long(edx,tmp_esp++);
	put_fs_long(eflags,tmp_esp++);
	put_fs_long(old_eip,tmp_esp++);
	current->blocked |= sa->sa_mask; /* 在调用该信号句柄之前，sa_mask信号集加进进程的信号屏蔽字中 */
}
